package com.fxd.conn;

import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 获取jedis连接工厂，和jedis实例
 */
@Slf4j
public class JedisConnUtil {

    public static final RedisConnectionFactory redisConnectionFactory;

    static {
        //redis连接配置
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();  //单机
        config.setHostName("192.168.56.10");
        config.setPort(6379);
        config.setDatabase(0);
//        config.setPassword(RedisPassword.of(""));  //密码

        //连接池配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(4);  //最大空闲jedis实例
        jedisPoolConfig.setMaxWaitMillis(1);  //当borrow一个jedis实例时，最大的等待时间，如果超过等待时间，则直接抛JedisConnectionException
        jedisPoolConfig.setMaxTotal(8);  //最多分配多少jedis实例
        jedisPoolConfig.setMinIdle(2);  //最小空闲jedis实例

        /**
         * 注意：即使配置了校验，仍然可能在高并发下出现问题！！！
         * 多线程情况下，可能会导致连接池的某些连接不可用
         * jedis判断是否连接可用源码
         * HostAndPort hostAndPort = (HostAndPort)this.hostAndPort.get();
         * String connectionHost = jedis.getClient().getHost();
         * int connectionPort = jedis.getClient().getPort();
         * return hostAndPort.getHost().equals(connectionHost) && hostAndPort.getPort() == connectionPort && jedis.isConnected() && jedis.ping().equals("PONG");
         */
        jedisPoolConfig.setTestOnBorrow(true);  //从连接池中获取实例时，校验连接是否可用，无用则销毁
        jedisPoolConfig.setTestOnReturn(true);  //归还连接池实例时，校验连接是否可用，无用则销毁
        jedisPoolConfig.setTestOnCreate(true);  //从连接池创建实例时，校验连接是否可用，无用则销毁

        //相当于一个定时任务，监控连接池中实例的可用性
//        jedisPoolConfig.setTestWhileIdle(true);  //如果为true，表示有一个idle object evitor线程对idle object进行扫描，如果validate失败，此object会被从pool中drop掉；这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
//        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(1);  //表示idle object evitor两次扫描之间要sleep的毫秒数

        //jedis客户端配置
        //JedisClientConfiguration没有提供构造方法，建造者模式创建一个构造器
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jedisPoolingClient = JedisClientConfiguration.builder().usePooling();  //使用连接池！！！
        //将构造器中的连接池替换掉
        jedisPoolingClient.poolConfig(jedisPoolConfig);
        JedisClientConfiguration jedisClient = jedisPoolingClient.build();

        redisConnectionFactory = new JedisConnectionFactory(config, jedisClient);
        ((JedisConnectionFactory) redisConnectionFactory).afterPropertiesSet();  //根据配置文件创建连接池！！！
    }

    //不允许外部通过构造函数创建实例
    private JedisConnUtil() {
    }

    /**
     * 通过lombok的@Cleanup注解自动释放jedis实例
     * 同步阻塞式IO，采用线程池时是线程安全的
     *
     * @return
     */
    public static Jedis getJedis() {
        @Cleanup Jedis jedis = null;
        try {
            jedis = (Jedis) redisConnectionFactory.getConnection().getNativeConnection();
        } catch (Exception e) {
            log.info("获取jedis实例出现异常 {}", e);
        }
        return jedis;
    }

    /**
     * 使用jedis实例必须释放资源！！！
     *
     * @param jedis
     */
    public static void closeJedis(Jedis jedis) {
        try {
            if (jedis != null) {
                jedis.close();
            }
        } catch (Exception e) {
            log.info("释放jedis实例出现异常 {}", e);
        }
    }

}
